#ifndef LOGGER_H
#define LOGGER_H

#include <pthread.h>
#include <stdarg.h>


#ifdef LOGGING_INFO
#define LOGGING
#define LOG_INFO(...) LOG(INFO, ##__VA_ARGS__)
#define LOG_WARNING(...) LOG(WARNING, ##__VA_ARGS__)
#define LOG_ERROR(...) LOG(ERROR, ##__VA_ARGS__)
#define LOG_FATAL(...) LOG(FATAL, ##__VA_ARGS__)
#define LOG_PANIC(...) LOG(PANIC, ##__VA_ARGS__)
#endif


#ifdef LOGGING_WARNING
#define LOGGING
#define LOG_WARNING(...) LOG(WARNING, ##__VA_ARGS__)
#define LOG_ERROR(...) LOG(ERROR, ##__VA_ARGS__)
#define LOG_FATAL(...) LOG(FATAL, ##__VA_ARGS__)
#define LOG_PANIC(...) LOG(PANIC, ##__VA_ARGS__)
#define LOG_INFO(...) ;
#endif


#ifdef LOGGING_ERROR
#define LOGGING
#define LOG_ERROR(...) LOG(ERROR, ##__VA_ARGS__)
#define LOG_FATAL(...) LOG(FATAL, ##__VA_ARGS__)
#define LOG_PANIC(...) LOG(PANIC, ##__VA_ARGS__)
#define LOG_INFO(...) ;
#define LOG_WARNING(...) ;
#endif


#ifdef LOGGING_FATAL
#define LOGGING
#define LOG_FATAL(...) LOG(FATAL, ##__VA_ARGS__)
#define LOG_PANIC(...) LOG(PANIC, ##__VA_ARGS__)
#define LOG_INFO(...) ;
#define LOG_WARNING(...) ;
#define LOG_ERROR(...) ;
#endif


#ifdef LOGGING_PANIC
#define LOGGING
#define LOG_PANIC(...) LOG(PANIC, ##__VA_ARGS__)
#define LOG_INFO(...) ;
#define LOG_WARNING(...) ;
#define LOG_ERROR(...) ;
#define LOG_FATAL(...) ;
#endif



#define LOG_FORMAT "LOG %s: in %s:%d in function <%s> :\t"

#ifdef LOGGING

static FILE *log_file;

#define BUF_SIZE 1024
#define MSG_SIZE 1024

static char log_buffer[BUF_SIZE][MSG_SIZE];
static int head;
static int tail;
static int temp;

static int logger_exit;

static pthread_t collector_thread;

void* collector(void* args)
{
	while(1) {
		usleep(1000);
		int t = tail;

		if(t < temp) {
			fprintf(log_file, "%s", log_buffer[t % BUF_SIZE]);
			fflush(log_file);
			__sync_bool_compare_and_swap (&tail, t, t + 1);
		} else {
			if(head == tail && logger_exit)
				return NULL;
		}
	}
}


void logger(const char* log_str, ...)
{
	if (!log_file) {
		//log_file = stderr;		
		printf("LOGGER NOT INITED\n");
		return;
	}
	
	while(1) {
		int h = head;
		if(h - BUF_SIZE < tail) {
			if(!__sync_bool_compare_and_swap (&head, h, h + 1)) {
				continue;
			}
			while(1) {
				if(!__sync_bool_compare_and_swap (&temp, h, h))
					continue;

				va_list args;
				va_start (args, log_str);
				vsprintf(log_buffer[h % BUF_SIZE], log_str, args);
				va_end (args);
				
				__sync_bool_compare_and_swap (&temp, h, h + 1);
				break;
			}
		}
		break;
	}
} 


#define LOG(log_type, log_str, ...) \
		logger(LOG_FORMAT log_str "\n", #log_type, __FILE__, __LINE__, __func__, ##__VA_ARGS__)

#define LOGGER_INIT(path) \
do { \
	log_file = fopen(path, "wt");\
	if(!log_file)\
		log_file = stdout;\
	if(pthread_create(&collector_thread, NULL, collector, NULL) < 0)\
		printf("LOGGER INIT FAILED\n");\
} while(0); \


#define LOGGER_WAIT() do{ pthread_join(collector_thread, NULL); } while(0)
#define LOGGER_EXIT() do{ __sync_bool_compare_and_swap (&logger_exit, 0, 1); } while(0)


#else

#define LOG_INFO(...) ;
#define LOG_WARNING(...) ;
#define LOG_ERROR(...) ;
#define LOG_FATAL(...) ;
#define LOG_PANIC(...) ;

#define LOG(log_type, log_str, ...) ;

#define LOGGER_INIT(path) ;
#define LOGGER_WAIT() ;
#define LOGGER_EXIT() ;

#endif


#endif //LOGGER_H
